/*=============================================================================
    jibits/dom.js
    Copyright (C) 2008-2009, Donald W. Griffin
    All rights reserved.
    [MIT license :: see license.txt for details]
=============================================================================*/
$module("jibits.dom");

$requires("zjs.browser");
$requires("zjs.text");

$requires("jibits.geom");

// TODO - look at jQuery css/curCSS methods

zjs.copyProps(Function.prototype,
{
    /**
    Schedules this method to be called a specified number of seconds later. To
    cancel a pending call, call nevermind on the same function object. For
    example:

        function foo () { }

        foo.later();
        //...
        foo.nevermind();

    It is important to note that for any given function object, only one later
    call is ever pending. Multiple calls to later simply do nothing until the
    initial later request has been dispatched.

    @param sec The number of seconds in the future to deliver the call. The
        default is 0.01 seconds (10 milliseconds).
    */
    later : function (sec)
    {
        if ("laterPending" in this)
            return;

        var method = this;
        var f = function ()
        {
            if (!("laterPending" in method)) // if (cancelled)
                return;
            delete method.laterPending;
            method();
        }

        zjs.addMeta(f, { binding : "later", target : method });

        if (arguments.length == 0)
            sec = 0.01;

        this.laterPending = window.setTimeout(f, sec * 1000);
    },

    /**
    Cancels any pending later call to this method. It is harmless to call this
    method even if there is no pending later call.

    @return True if a pending later call was cancelled, false if not.
    */
    nevermind : function ()
    {
        if (!("laterPending" in this)) // if (cancelled already)
            return false;

        window.clearTimeout(this.laterPending);
        delete this.laterPending;
        return true;
    }
});

//-----------------------------------------------------------------------------

function $elem (id)
{
    if (typeof(id) == "string") // handle ID -> Element (90% case)
        return document.getElementById(id);
    if (jibits.isWidget(id)) // handle Widget -> Element
        return id.elem();
    return id; // already an Element (probably)
}

$namespace(jibits, {

/**
This function evals code in a scope free of local variables. The result is then
returned. This is important as local variables can hide global ones.
*/
_eval : function ()
{
    return eval(arguments[0]);
},

/**
Adds properties to the $meta.jibits container of the given object.

@param obj The object to which meta data is to be added.
@param meta The object containing the meta data properties to add.
*/
addMeta : function (obj, meta)
{
    zjs.copyProps(jibits.getMeta(obj), meta);
    return obj;
},

/**
Returns the $meta data property of the given object, adding one as needed. The
meta data for Jibits is stored in the ZJS meta object ($meta.jibits).

@param obj The object for which meta data is desired.
@param noadd When true, $meta is returned only if already present.
@return The meta data object for the given object.
*/
getMeta : function (obj, noadd)
{
    var meta = zjs.getMeta(obj, noadd), mj;
    if (meta && !(mj = meta.jibits) && !noadd)
        meta.jibits = mj = {};
    return mj;
},

/**
Returns true if the given object is a Widget-like object.

@param obj The object in question.
@return True if the given object is a Widget-like object.
*/
isWidget : function (obj)
{
    return obj && obj.elem instanceof Function;
},

// modification notifiers (here so they can be overridden)

beginMod : function (el)
{
},

endMod : function (el, nochange)
{
},

wasMod : function (el)
{
},

// attributes

_xlatAttrHas : { },
_xlatAttrSet :
{
    names : { className : "class", htmlFor : "for" },
    values : { }
},

getAttr : function (el, name) // Prototype#readAttribute
{
    el = $elem(el);
    return el.getAttribute(name);
},

getText : function (el)
{
    var ret = "";
    el = $elem(el);

    // remove all TEXT nodes (we're replacing the text):
    for (var child = el.firstChild; child; child = child.nextSibling)
        if (child.nodeType == 3) // if (TEXT)
            ret += child.nodeValue;

    return ret;
},

hasAttr : function (el, attr)
{
    el = $elem(el);
    if (el.hasAttribute)
        return el.hasAttribute(attr);
    attr = jibits._xlatAttrHas[attr] || attr;
    var node = el.getAttributeNode(attr);
    return !!(node && node.specified);
},

setAttr : function (el, name, value) // Prototype#writeAttribute
{
    el = $elem(el);
    var attributes = { }, t = jibits._xlatAttrSet, nochange = true;

    if (typeof name == 'object')
        attributes = name;
    else
        attributes[name] = (value === undefined) ? true : value;

    jibits.beginMod(el);
    try
    {
        for (var attr in attributes)
        {
            value = attributes[attr];

            switch (attr)
            {
             case "$html":
                 // TODO - onremove children first
                 el.innerHTML = value;
                 nochange = false;
                 continue;
             case "$text": jibits.setText(el, value); continue;
             case "style": jibits.setStyle(el, value); continue;
            }

            name = t.names[attr] || attr;

            if (t.values[attr])
                name = t.values[attr](el, value);

            if (value === false || value === null)
            {
                el.removeAttribute(name);
                nochange = false;
            }
            else if (typeof(value) == "function")
                // probably an event handler (e.g., "onclick")
                el[name] = value;
            else
            {
                if (value === true)
                    value = name;

                if (el.getAttribute(name) != value)
                    nochange = false;

                el.setAttribute(name, value);
            }
        }
    }
    finally
    {
        jibits.endMod(el, nochange);
    }

    return el;
},

setText : function (el, text)
{
    el = $elem(el);

    // remove all TEXT nodes (we're replacing the text):
    for (var next, child = el.firstChild; child; child = next)
    {
        next = child.nextSibling;
        if (child.nodeType == 3) // if (TEXT)
            el.removeChild(child);
    }

    el.appendChild(el.ownerDocument.createTextNode(text));

    jibits.wasMod(el);

    return el;
},

// size & position

getSize : function (el)
{
    el = $elem(el);

    var display = jibits.getStyle(el, 'display');
    if (display != 'none' && display != null) // Safari bug
        return new jibits.Size(el.offsetWidth, el.offsetHeight);

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    var els = el.style;
    var oldV = els.visibility, oldP = els.position, oldD = els.display;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = 'block';
    //var w = el.clientWidth, h = el.clientHeight; // Prototype used these...
    var w = el.offsetWidth, h = el.offsetHeight;
    els.display = oldD;
    els.position = oldP;
    els.visibility = oldV;

    return new jibits.Size(w, h);
},

getWidth : function (el)
{
    var size = jibits.getSize(el);
    return size.w;
},

getHeight : function (el)
{
    var size = jibits.getSize(el);
    return size.h;
},

getOffsetParent : function (el)
{
    el = $elem(el);

    if (el.offsetParent)
        return el.offsetParent;

    var body = document.body;
    if (el != body)
        while ((el = el.parentNode) && el != body)
            if (!jibits.isPosStatic(el))
                return el;

    return body;
},

getPosOffset : function (el) // Prototype#positionedOffset
{
    el = $elem(el);

    var L = 0, T = 0;

    for (var op; el; el = op)
    {
        L += el.offsetLeft || 0;
        T += el.offsetTop  || 0;

        if ((op = el.offsetParent))
        {
            R += op.offsetWidth  - el.offsetLeft - el.offsetWidth;
            B += op.offsetHeight - el.offsetTop  - el.offsetHeight;
        }

        if (op)
        {
            if (jibits.isBody(op))
                break;
            if (!jibits.isPosStatic(op))
                break;
        }
    }

    var ret = $point(L, T);
    ret.alt = $point(R, B);
    return ret;
},

getPosOffsetRect : function (el)
{
    el = $elem(el);
    var pt = jibits.getPosOffset(el), sz = jibits.getSize(el);
    var ret = $rect(pt, sz);
    ret.alt = $rect(pt.alt.x, pt.alt.y, sz.w, sz.h);
    return ret;
},

getScrollOffset : function (el) // Prototype#cumulativeScrollOffset
{
    el = $elem(el);

    var L = 0, T = 0;

    for ( ; el; el = el.parentNode)
    {
        L += el.scrollLeft || 0;
        T += el.scrollTop  || 0;
    }

    return $point(L, T);
},

getStylePix : function (el, st)
{
    var v = jibits.getStyle(el, st);
    return (v && v.endsWith("px")) ? parseInt(v) : 0;
},

getTotalOffset : function (el) // Prototype#cumulativeOffset
{
    el = $elem(el);

    // Safari returns margins on body which is incorrect if the child is
    // absolutely positioned. Opera applies border to offsetLeft/Top.
    var noBodyAbs = zjs.browser.safari, addBorders = !zjs.browser.opera;
    var x, y, T = 0, L = 0, R = 0, B = 0, getPix = jibits.getStylePix;
    for (var op; el; el = op)
    {
        x = el.offsetLeft || 0;
        y = el.offsetTop  || 0;

        if ((op = el.offsetParent))
        {
            if (addBorders) // Prototype.js didn't handle these
            {
                x += getPix(op,"border-left-width");
                y += getPix(op,"border-top-width");
            }

            R += op.offsetWidth  - x - el.offsetWidth;
            B += op.offsetHeight - y - el.offsetHeight;

            if (noBodyAbs && op == document.body && jibits.isPosAbs(el))
                op = null;
        }

        L += x;
        T += y;
    }

    var ret = $point(L, T);
    ret.alt = $point(R, B);
    return ret;
},

getTotalOffsetRect : function (el)
{
    el = $elem(el);
    var pt = jibits.getTotalOffset(el), sz = jibits.getSize(el);
    var ret = $rect(pt, sz);
    ret.alt = $rect(pt.alt.x, pt.alt.y, sz.w, sz.h);
    return ret;
},

getViewportOffset : function (el) // Prototype#viewportOffset
{
    el = $elem(el);

    var L = 0, T = 0;

    for (var op = el; op; op = op.offsetParent)
    {
        L += element.offsetLeft || 0;
        T += element.offsetTop  || 0;

        if (op.offsetParent == document.body && jibits.isPosAbs(op))
            break; // safari fix
    }

    for (var pn = el; pn; pn = pn.parentNode)
        if (!zjs.browser.opera || jibits.isBody(pn))
        {
            L -= pn.scrollLeft || 0;
            T -= pn.scrollTop  || 0;
        }

    return $point(L, T);
},

moveTo : function (el, r, pos)
{
    el = $elem(el);
    pos = pos || jibits.Positioning.TL;
    
    var coords = pos.pack(r), nochange = true;

    jibits.beginMod(el);
    try
    {
        for (var n in coords)
            if (coords[n] != el.style[n])
            {
                el.style[n] = coords[n] + "px";
                nochange = false;
            }
    }
    finally
    {
        jibits.endMod(el, nochange);
    }

    return el;
},

placeAdjacent : function (el, elp, ref, refp, pos)
{
    el = $elem(el);
    pos = pos || jibits.Positioning.TL;

    var r = jibits.getTotalOffsetRect(ref);
    refp = jibits.Placement.from(refp);
    var pt = refp.anchor(r);

    elp = jibits.Placement.from(elp);
    r = jibits.getTotalOffsetRect(el);
    //console.info("el.r="+r.stringify());
    r = pos.xlatRect(r);
    // TODO - only correct for placing elements at body level(?)
    //console.info("el.xr="+r.stringify());

    elp.place(r, pt);

    return jibits.moveTo(el, r, pos);
},

placeCenter : function (el, ref, coords, pos)
{
    el = $elem(el);
    pos = pos || jibits.Positioning.TL;

    var sz = jibits.getSize(el);
    var r = ref ? jibits.getTotalOffsetRect(ref)
                : $rect(0, 0, document.body.clientWidth,
                              document.body.clientHeight);

    var x = (coords && "x" in coords) ? coords.x : ((r.W() - sz.W()) / 2);
    var y = (coords && "y" in coords) ? coords.y : ((r.H() - sz.H()) / 2);

    r.horz.set(x, sz.W());
    r.vert.set(y, sz.H());

    r = pos.xlatRect(r);
    return jibits.moveTo(el, r, pos);
},

// Styles

getOpacity : function (el)
{
    return jibits.getStyle(el, 'opacity');
},

setOpacity : function (el, v)
{
    el = $elem(el);
    var o = (v == 1 || v === '') ? '' : (v < 0.00001) ? 0 : v;

    if (el.style.opacity != o)
    {
        el.style.opacity = o;
        jibits.wasMod(el);
    }

    return el;
},

getStyle : function (el, style)
{
    el = $elem(el);
    style = (style == 'float') ? 'cssFloat' : style.camelize();
    var value = el.style[style];
    if (!value || value == 'auto')
    {
        var css = document.defaultView.getComputedStyle(el, null);
        value = css ? css[style] : null;
    }
    if (style == 'opacity')
        return value ? parseFloat(value) : 1.0;
    return value == 'auto' ? null : value;
},

setStyle : function (el, styles)
{
    el = $elem(el);
    var els = el.style, nochange = true;

    jibits.beginMod(el);
    try
    {
        for (var s in styles)
        {
            var v = styles[s];
            if (s == 'opacity')
                jibits.setOpacity(el, v);
            else
            {
                if (s == 'float' || s == 'cssFloat')
                    s = (typeof(els.styleFloat) == "undefined") ? 'cssFloat' : 'styleFloat';

                if (els[s] != v)
                {
                    els[s] = v;
                    nochange = false;
                }
            }
        }
    }
    finally
    {
        jibits.endMod(el, nochange);
    }

    return el;
},

// visibility

isVisible : function (el)
{
    el = $elem(el);
    return el.style.display != "none";
},

hide : function (el)
{
    return jibits.setVisible(el, false);
},

setVisible : function (el, b)
{
    return jibits.setStyle(el, { display : b ? "" : "none" });
},

show : function (el)
{
    return jibits.setVisible(el, true);
},

toggleVisible : function (el)
{
    return jibits.setVisible(el, !jibits.isVisible(el));
},

// CSS classes

addClasses : function (el, add)
{
    return jibits.modifyClasses(el, add);
},

getClasses : function (el)
{
    el = $elem(el);
    var s = zjs.safeStr(el.className);
    return (s.length > 0) ? s.split(/\s+/) : [];
},

hasClass : function (el, name)
{
    el = $elem(el);
    var c = el.className;
    return !zjs.isEmpty(c) &&
            (c == name || new RegExp("(^|\\s)"+name+"(\\s|$)").test(c));
},

modifyClasses : function (el, update)
{
    var hash = {}, names = [], newval = [];
    $foreach(update, function (s)
    {
        var c = s.charAt(0);
        if (c === "+" || c === "-" || c === "^")
            s = s.substring(1);
        else
            c = "+";

        hash[s] = c;
        names.push(s);
    });

    el = $elem(el);
    $foreach(jibits.getClasses(el), function (c)
    {
        var op = hash[c];
        if (!op || op === "+")
            newval.push(c);
        hash[c] = null;
    });

    $foreach(names, function (n)
    {
        var op = hash[n];
        if (op === "+" || op === "^")
            newval.push(n);
    });

    var s = newval.join(" ");
    if (el.className != s)
    {
        el.className = s;
        jibits.wasMod(el);
    }

    return el;
},

removeClasses : function (el, rem)
{
    var update = [];
    $foreach(rem, function (c)
    {
        update.push((c.charAt(0) === "-") ? c : ("-" + c));
    })

    return jibits.modifyClasses(el, update);
},

// Positioning

moveAbove : function (el, ref)
{
    ref = $elem(ref);
    if (ref.nextSibling)
        jibits.moveBelow(el, ref.nextSibling);
    else
        jibits.moveToTop(el);

    return el;
},

moveBelow : function (el, ref)
{
    el = $elem(el);
    ref = $elem(ref);
    var pn = el.parentNode;
    if (el.parentNode === pn && el.nextSibling === ref)
        return el;

    pn.removeChild(el);
    pn.insertBefore(el, ref);

    jibits.wasMod(el);
    return el;
},

moveToTop : function (el)
{
    el = $elem(el);
    if (el.nextSibling === null)
        return el;

    var pn = el.parentNode;
    pn.removeChild(el);
    pn.appendChild(el);

    jibits.wasMod(el);
    return el;
},

scrollTo : function (el)
{
    var offset = jibits.getTotalOffset(el);
    window.scrollTo(offset.x, offset.y);
    return el;
},

// Misc.

getTag : function (el)
{
    el = $elem(el);
    return el ? el.tagName.toLowerCase() : "";
},

isBody : function (el)
{
    var tn = jibits.getTag(el);
    return tn == 'body';
},

isPos : function (el, t1, t2)
{
    var p = zjs.safeStr(jibits.getStyle(el, "position"));
    return p === t1 || (t2 !== undefined && p === t2);
},

isPosAbs : function (el) { return jibits.isPos(el, "absolute"); },
isPosFix : function (el) { return jibits.isPos(el, "fixed"); },
isPosRel : function (el) { return jibits.isPos(el, "relative"); },
isPosStatic : function (el) { return jibits.isPos(el, "static"); },

// DOM mechanics

// removes whitespace-only text node children
cleanWhitespace : function (el)
{
    el = $elem(el);
    var regex = /\S/;

    for (var next, n = el.firstChild; n; n = next)
    {
        next = n.nextSibling;
        if (n.nodeType == 3 && !regex.test(n.nodeValue))
            el.removeChild(n);
    }

    return el;
},

/**
Clones an element and optionally fixes it up. That is, unless the config.nofix
option is passed. If not, the cloned element (tree) will be "fixed" before return.
See jibits.cloneFixer for details regarding config.
*/
cloneElem : function (src, config)
{
    src = $elem(src);
    config = config || {};
    var ret = src && src.cloneNode(config.deep);

    if (!("nofix" in config))
        jibits.fixClone(ret, config);

    return ret;
},

cloneElemDeep : function (src, config)
{
    config = config || {};
    config.deep = true;
    return jibits.cloneElem(src, config);
},

cloneElemShallow : function (src, config)
{
    config = config || {};
    config.deep = false;
    return jibits.cloneElem(src, config);
},

/**
This method will fix a cloned element. The primary user of this method is the
fixClone method.

All cloned elements have a couple problems:

    - the ID is copied (and hence not unique)
    - the $meta data is copied (and is bogus)

The correction for $meta is simple: remove it. The ID correction is a bit more
intricate. There are essentially three strategies for correcting the ID:

    1. Set the ID of the root element to "foo" and prepend "foo_" to all child
        elements that have ID's. The following config object will result in this
        behavior:

            { id : "foo" }

    2. Prepend a "foo_" to all elements, including the root. The following config
        object will result in this behavior:

            { prefix : "foo_" }

    3. Ensure a unique ID given a base ID (e.g., "foo"). If the root element has
        no ID, the first unused ID of "foo", "foo1", "foo2", etc. will be applied
        using strategy #1. If the root element has an ID (e.g., "bar"), the first
        unused ID of "foo_bar", "foo1_bar", "foo2_bar", etc. will be applied using
        strategy #2.

        The following config object will result in this behavior:

            { baseid : "foo_" }

This method is designed to be a callback method used by visitElemTree. As such,
the config object is used to pass information between calls as each element in
the tree is traversed. It is, therefore, proper to use different config instances
for each call.
*/
cloneFixer : function (el, config)
{
    if ("$meta" in el)
        el.$meta = null;
    if (!config)
        return;

    if (config.baseid)
    {
        var id = zjs.safeStr(el.id), hasid = !zjs.isEmpty(id), sep = hasid ? "_" : "";

        for (var i = 1, pfx = config.baseid + sep; $elem(pfx + id); ++i)
            pfx = config.baseid + "$" + i + sep;

        config.baseid = null;
        if (hasid)
            config.prefix = pfx;
        else
            config.id = pfx;
    }

    if (config.id)
    {
        config.prefix = (el.id = config.id) + "_";
        config.id = null;
    }
    else if (config.prefix && !zjs.isEmpty(el.id))
        el.id = config.prefix + el.id;
},

fixClone : function (el, config)
{
    return jibits.visitElemTree(el, jibits.cloneFixer.tail(config));
},

newElem : function (tag, id, attrs)
{
    var el = document.createElement(tag);
    el.id = id;
    if (attrs)
    {
        var keep = attrs.style;
        if (keep)
            delete attrs.style;

        zjs.copyProps(el, attrs);

        if (keep)
        {
            jibits.setStyle(el, keep);
            attrs.style = keep;
        }
    }

    return el;
},

newChild : function (pel, tag, id, attrs)
{
    var cel = jibits.newElem(tag, id, attrs);
    pel = $elem(pel);
    pel.appendChild(cel);
    return cel;
},

newChildDiv : function (pel, id, attrs)
{
    return jibits.newChild(pel, "div", id, attrs);
},

newDiv : function (id, attrs)
{
    return jibits.newElem("div", id, attrs);
},

newParent : function (cel, tag, id, attrs)
{
    var pel = jibits.newElem(tag, id, attrs);
    if (typeof(cel) == "string")
        pel.innerHTML = cel;
    else
    {
        var cp = cel.parentNode;
        if (cp)
            cp.replaceChild(pel, cel);
        pel.appendChild(cel);
    }
    return pel;
},

newParentDiv : function (cel, id, attrs)
{
    return jibits.newParent(cel, "div", id, attrs);
},

removeChildElems : function (el)
{
    el = $elem(el);
    if (el)
        for (var next, cel = el.firstChild; cel; cel = next)
        {
            next = cel.nextSibling;
            if (cel.nodeType == 1) // if (ELEMENT)
                jibits.removeElem(cel);
        }
    return el;
},

removeElem : function (el)
{
    el = $elem(el);
    if (!el)
        return el;

    jibits.removeNotify(el);

    if (el.parentNode)
        el.parentNode.removeChild(el);

    return el;
},

removeNotify : function ()
{
    function recurse (el)
    {
        if (el.nodeType != 1) // if (not ELEMENT)
            return;

        for (var next, cel = el.firstChild; cel; cel = next)
        {
            next = cel.nextSibling;
            recurse(cel);
        }

        var meta = jibits.getMeta(el, true);
        if (meta && meta.onremove)
        {
            $foreach(meta.onremove, function (fn) { fn(el); });
            delete meta.onremove;
        }
    }

    return function (el)
    {
        el = $elem(el);
        if (el)
            recurse(el);
        return el;
    };
}(),

removeSubscribe : function (el, fn)
{
    el = $elem(el);
    $assert(el.nodeType == 1) // assert(ELEMENT)
    var meta = jibits.getMeta(el);
    if (meta.onremove)
        meta.onremove.push(fn);
    else
        meta.onremove = [ fn ];
    return el;
},

visitElemTree : function (el, visitor)
{
    visitor(el);

    for (var next, c = el.firstChild; c; c = next)
    {
        next = c.nextSibling; // in case visitor method removes the element
        if (c.nodeType == 1) // if (ELEMENT)
            jibits.visitElemTree(c, visitor);
    }

    return el;
}

}); // namespace jibits

//-----------------------------------------------------------------------------
// Viewport

$namespace(jibits, function ()
{
    function getDim (WH)
    {
        var br = zjs.browser;

        if (br.webkit && !document.evaluate)
            // Safari <3.0 needs self.innerWidth/Height
            return self['inner' + WH];

        if (br.opera && parseFloat(window.opera.version()) < 9.5)
            // Opera <9.5 needs document.body.clientWidth/Height
            return document.body['client' + WH]

        return document.documentElement['client' + WH];
    }

    function getH ()
    {
        return getDim("Height");
    }

    function getW ()
    {
        return getDim("Width");
    }

    return (
    {
        getViewportScroll : function ()
        {
            // see http://www.quirksmode.org/dom/w3c_cssom.html
            return $point(
              window.pageXOffset || document.documentElement.scrollLeft ||
                  document.body.scrollLeft,
              window.pageYOffset || document.documentElement.scrollTop ||
                  document.body.scrollTop
            );
        },

        getViewportSize : function ()
        {
            return $size(getW(), getH());
        },

        getViewportHeight : getH,
        getViewportWidth : getW
    });

}()); // namespace jibits

//-----------------------------------------------------------------------------
// Browser Hacks

if (zjs.browser.ie)
    $namespace (jibits, function ()
    {
        function detached (el)
        {
            // IE throws an error if element is not in document
            try { el = el.offsetParent; } catch(e) { return true; }
            return false;
        }

        function offsetHelper (el, sooper)
        {
            el = $elem(el);

            if (detached(el))
                return $point();

            var position = jibits.getStyle(el, 'position');
            if (position !== 'static')
                return sooper(el);

            // Trigger hasLayout on the offset parent so that IE6 reports
            // accurate offsetTop and offsetLeft values for position: fixed.
            var op = jibits.getOffsetParent(el);
            if (op && jibits.isPosFix(op))
                op.setStyle({ zoom: 1 });
            jibits.setStyle(el, { position: 'relative' });
            var value = sooper(el);
            jibits.setStyle(el, { position: position });
            return value;
        }

        function stripAlpha (filter)
        {
            return filter.replace(/alpha\([^\)]*\)/gi,'');
        }

        function _getAttr (el, attribute)
        {
            return el.getAttribute(attribute, 2);
        }

        function _getAttrNode (el, attribute)
        {
            var node = el.getAttributeNode(attribute);
            return node ? node.value : "";
        }

        function _getEv (el, attribute)
        {
            attribute = el.getAttribute(attribute);
            return attribute ? attribute.toString().slice(23, -2) : null;
        }

        function _flag (el, attribute)
        {
            return $(el).hasAttribute(attribute) ? attribute : null;
        }

        jibits._xlatAttrSet =
        {
            names :
            {
                cellpadding: 'cellPadding',
                cellspacing: 'cellSpacing',
                'class': 'className',
                'for':   'htmlFor'
            },
            values:
            {
                checked : function (el, v) { el.checked = !!v; },
                style : function (el, v) { el.style.cssText = v ? v : ''; }
            }
        };

        $foreach(["colSpan","rowSpan","vAlign","dateTime","accessKey","tabIndex ",
                  "encType","maxLength","readOnly","longDesc","frameBorder"],
            function (s)
            {
                var lwr = s.toLowerCase();
                jibits._xlatAttrSet.names[lwr] = jibits._xlatAttrHas[lwr] = s;
            });

        jibits._xlatAttrGet =
        {
            names :
            {
                "class" : "className",
                "for" : "htmlFor"
            },

            values :
            {
                href:        _getAttr,
                src:         _getAttr,
                type:        _getAttr,
                action:      _getAttrNode,
                disabled:    _flag,
                checked:     _flag,
                readonly:    _flag,
                multiple:    _flag,
                onload:      _getEv,
                onunload:    _getEv,
                onclick:     _getEv,
                ondblclick:  _getEv,
                onmousedown: _getEv,
                onmouseup:   _getEv,
                onmouseover: _getEv,
                onmousemove: _getEv,
                onmouseout:  _getEv,
                onfocus:     _getEv,
                onblur:      _getEv,
                onkeypress:  _getEv,
                onkeydown:   _getEv,
                onkeyup:     _getEv,
                onsubmit:    _getEv,
                onreset:     _getEv,
                onselect:    _getEv,
                onchange:    _getEv,
                style : function(el) { return el.style.cssText.toLowerCase(); },
                title : function(el) { return el.title; }
            }
        }

        return (
        {
            getAttr : $override(function (el, name) // Prototype#readAttribute
            {
                el = $elem(el);

                var t = jibits._xlatAttrGet;

                if (t.values[name])
                    return t.values[name](el, name);
                if (t.names[name])
                    name = t.names[name];
                if (name.include(':'))
                    return (!el.attributes || !el.attributes[name])
                              ? null : el.attributes[name].value;

                return $super(arguments)(el, name);
            }),

            getOffsetParent : $override(function (el)
            {
                el = $elem(el);

                if (detached(el))
                    return document.body;

                var pos = jibits.getStyle(el, 'position');
                if (pos !== 'static')
                    return $super(arguments)(el);

                jibits.setStyle(el, { position: 'relative' });
                var value = $super(arguments)(el);
                jibits.setStyle(el, { position: pos });
                return value;
            }),

            getPosOffset : $override(function (el) // Prototype#positionedOffset
            {
                return offsetHelper(el, $super(arguments));
            }),

            getViewportOffset : $override(function (el) // Prototype#viewportOffset
            {
                return offsetHelper(el, $super(arguments));
            }),

            getStyle : $replace(function (el, style)
            {
                el = $elem(el);
                style = (style == 'float' || style == 'cssFloat')
                            ? 'styleFloat' : style.camelize();

                var value = el.style[style];
                if (!value && el.currentStyle)
                    value = el.currentStyle[style];

                if (style == 'opacity')
                {
                    value = (jibits.getStyle(el, 'filter') || '').
                                  match(/alpha\(opacity=(.*)\)/);
                    if (value && value[1])
                        return parseFloat(value[1]) / 100;
                    return 1.0;
                }

                if (value == 'auto')
                {
                    if ((style == 'width' || style == 'height')
                            && jibits.isVisible(el))
                        return el['offset' + style.capitalize()] + 'px';

                    return null;
                }

                return value;
            }),

            getTotalOffset : $override(function (el) // Prototype#cumulativeOffset
            {
                el = $elem(el);

                if (detached(el))
                    return $point();

                return $super(arguments)(el);
            }),

            setOpacity : $replace(function (el, value)
            {
                el = $elem(el);
                var cs = el.currentStyle, style = el.style;
                if (cs ? !cs.hasLayout : (style.zoom == 'normal'))
                    style.zoom = 1;

                var filter = jibits.getStyle(el, 'filter');
                var s = stripAlpha(filter);
                if (!(value == 1 || value === ''))
                {
                    if (value < 0.00001)
                        value = 0;
                    style.filter = s + 'alpha(opacity=' + (value * 100) + ')';
                }
                else if (zjs.isEmpty(s))
                    style.removeAttribute('filter');
                else
                    style.filter = s;

                return el;
            })
        });
    }());
else if (zjs.browser.webkit)
    $namespace (jibits,
    {
        setOpacity : $override(function (el, v)
        {
            el = $super(arguments)(el, v);

            if (v != 1)
                return el;

            if (jibits.getTag(el) == 'img' && el.width)
            {
                el.width++;
                el.width--;
            }
            else
                try
                {
                    var n = document.createTextNode(' ');
                    el.appendChild(n);
                    el.removeChild(n);
                } catch (e) { }

            return el;
        })
    });
else if (zjs.browser.opera)
    $namespace (jibits,
    {
        getAttr : $override(function (el, name)
        {
            el = $elem(el);
            if (name === "title")
                return el.title;

            return $super(arguments)(el, name);
        }),

        getStyle : $override(function (el, style)
        {
            el = $elem(el);
            switch (style)
            {
                case 'left': case 'top': case 'right': case 'bottom':
                    if (jibits.isPosStatic(el))
                        return null;
                    // fall...

                case 'height': case 'width':
                    // returns '0px' for hidden elements; we want it to return null
                    if (!jibits.isVisible(el))
                        return null;

                    // returns the border-box dimensions rather than the content-box
                    // dimensions, so we subtract padding and borders from the value
                    var st = $super(arguments)(el, style);
                    var dim = parseInt(st, 10);

                    if (dim === el['offset' + style.capitalize()])
                    {
                        var props = (style === 'height')
                            ? ['border-top-width', 'padding-top',
                               'padding-bottom', 'border-bottom-width']
                            : ['border-left-width', 'padding-left',
                               'padding-right', 'border-right-width'];

                        for (var i = 0, n = props.length; i < n; ++i)
                        {
                            var val = jibits.getStyle(el, props[i]);
                            if (val !== null)
                                dim -= parseInt(val, 10);
                        }
                    }

                    return dim + 'px';
            }

            return $super(arguments)(el, style);
        })
    });

// TODO - IE/Opera cannot do innerHTML on TABLE/SELECT (see Prototype)
